#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <aio.h>
#include <errno.h>
#include <aio_misc.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>

#include <rtems/system.h>
#include <rtems/seterr.h>

typedef struct rtems_async_waitlist
{

  int counter;
  struct sigevent sigev;
  rtems_chain_control waitlist;

} rtems_async_waitlist;


int
lio_listio (int mode,
            struct aiocb *const list[], int nent, struct sigevent *sig)
{
  struct sigevent defsigev;
  rtems_aio_request *requests[nent];

  int cnt;
  volatile int total = 0;
  int result = 0;

  if (sig == NULL) {
    defsigev.sigev_notify = SIGEV_NONE;
    sig = &defsigev;
  }

  pthread_mutex_lock (&aio_requests_mutex);

  for (cnt = 0; cnt < nent; ++cnt)
    if (list[cnt] != NULL && list[cnt]->aio_lio_opcode != LIO_NOP) {
    
      list[cnt]->aio_sigevent.sigev_notify = SIGEV_NONE;

      requests[cnt] = rtems_aio_enqueue_request ((struct aiocb *) list[cnt],
                                           list[cnt]->aio_lio_opcode);

      if (requests[cnt] != NULL)
        ++total;
      else
        result = -1;

    } else
      requests[cnt] = NULL;

  if (total == 0) {
    pthread_mutex_unlock (&aio_requests_mutex);

    if (mode == LIO_WAIT)
      rtems_aio_notify_only (sig, pthread_self());

    return result;

  }
  /* wait until all I/O is complete and the sig argument shall be ignored. */
  else if (mode == LIO_WAIT) {
    rtems_aio_waitlist *wait_node;
    int oldstate;
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    total = 0;
    for (cnt = 0; cnt < nent; ++cnt) {

      assert (requests[cnt] == NULL || list[cnt] != NULL);

      if (requests[cnt] != NULL && list[cnt]->aio_lio_opcode != LIO_NOP) {
        wait_node = malloc (sizeof (rtems_aio_waitlist));

        wait_node->cond = &cond;
        wait_node->result = &result;
        wait_node->counterp = &total;
        wait_node->sigevp = NULL;

        rtems_chain_append (&requests[cnt]->waitlist, &wait_node->next);
        ++total;
      }

    }
    pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &oldstate);

    while (total > 0)
      pthread_cond_wait (&cond, &aio_requests_mutex);

    pthread_setcancelstate (oldstate, NULL);

    if (pthread_cond_destroy (&cond) != 0)
      abort ();

    if (result != 0) {
      errno = (result == EINTR ? EINTR : EIO);
      result = -1;
    }
  } else {
    rtems_async_waitlist *waitlist;
    rtems_aio_waitlist *wait_node;

    waitlist = malloc (sizeof (rtems_async_waitlist));

    if (waitlist == NULL) {
      errno = EAGAIN;
      result = -1;
    } else {
      rtems_chain_initialize_empty (&waitlist->waitlist);
      pthread_t caller_thread;
      total = 0;

      for (cnt = 0; cnt < nent; ++cnt) {
        assert (requests[cnt] == NULL || list[cnt] != NULL);

        if (requests[cnt] != NULL && list[cnt]->aio_lio_opcode != LIO_NOP) {
          wait_node = malloc (sizeof (rtems_aio_waitlist));

          wait_node->cond = NULL;
          wait_node->result = NULL;
          wait_node->counterp = &waitlist->counter;
          wait_node->sigevp = &waitlist->sigev;
          wait_node->caller_thread = pthread_self();

          rtems_chain_append (&requests[cnt]->waitlist, &wait_node->next);
          rtems_chain_append (&waitlist->waitlist, &wait_node->async_next);
          ++total;

        }
      }

      waitlist->counter = total;
      waitlist->sigev = *sig;
    }

  }

  pthread_mutex_unlock (&aio_requests_mutex);

  return result;
}
